package fastdht

import (
	"fmt"
	log "github.com/golang/glog"
	"net"
	"strconv"
)

var _ = log.Info
var _ = fmt.Println
var _ = net.Listen
var BOOTSTRAP []string = []string{
	"67.215.246.10:6881",
	"212.129.33.50:6881",
	"82.221.103.244:6881"}

func NewDHT(ip string, port int) (dht *DHT, err error) {
	dht = new(DHT)
	dht.Ip = ip
	dht.Port = port

	dht.NodeId = randNodeId()

	table := newTable(maxTableNum)
	taddress := ip + ":" + strconv.Itoa(port)
	address, _ := net.ResolveUDPAddr("udp4", taddress)

	dht.Address = address
	dht.Table = table

	return
}

func (dht *DHT) sendFindNode(node *Node) {
	query := new(queryMessage)
	query.T = tranId()
	query.Q = "find_node"
	query.Y = "q"
	var nid string
	if node.nid != nil {
		nid = Neighbor(node.nid.String(), dht.NodeId.String())
	} else {
		nid = randNodeId().String()
	}

	query.A = map[string]interface{}{
		"id":     nid,
		"target": randNodeId().String(),
	}
	log.V(5).Infof("Port:%d 正在向地址%s发出Find Node请求", dht.Port, node.address.String())
	sendMsg(dht.Conn, node.address, query)
}
func (dht *DHT) replyPing(node *Node) {
	response := new(replyMessage)
	response.T = node.tid
	response.Y = "r"
	response.R = map[string]interface{}{
		"id": dht.NodeId.String(),
	}
	log.V(5).Infof("Port:%d 正在回复从地址%s发出Ping请求", dht.Port, node.address.String())
	sendMsg(dht.Conn, node.address, response)
}
func (dht *DHT) replyFindNode(node *Node) {
	response := new(replyMessage)
	response.T = node.tid
	response.Y = "r"
	response.R = map[string]interface{}{
		"id":    dht.NodeId.String(),
		"nodes": "",
	}
	log.V(5).Infof("Port:%d 正在回复从地址%s发出FindNode请求", dht.Port, node.address.String())
	sendMsg(dht.Conn, node.address, response)
}
func (dht *DHT) replyGetPeers(node *Node, infohash string, out chan string) {
	token := infohash[:tokenLength]
	response := new(replyMessage)
	response.T = node.tid
	response.Y = "r"
	response.R = map[string]interface{}{
		"id":    Neighbor(infohash, dht.NodeId.String()),
		"nodes": "",
		"token": token,
	}
	out <- infohash
	log.V(5).Infof("Port:%d 正在回复从地址%s发出GetPeers请求", dht.Port, node.address.String())
	sendMsg(dht.Conn, node.address, response)
}
func (dht *DHT) replyAnnouncePeer(node *Node, infohash string, token string, out chan string) {
	if token == infohash[:tokenLength] {
		out <- infohash
	}

	response := new(replyMessage)
	response.T = node.tid
	response.Y = "r"
	response.R = map[string]interface{}{
		"id": Neighbor(node.nid.String(), dht.NodeId.String()),
	}
	log.V(5).Infof("Port:%d 正在回复从地址%s发出AnnouncePeer请求", dht.Port, node.address.String())
	sendMsg(dht.Conn, node.address, response)
}
func (dht *DHT) replyError(node *Node) {
	response := new(errorMessage)
	response.T = node.tid
	response.Y = "e"
	response.E = []interface{}{
		202, "Server Error",
	}
	sendMsg(dht.Conn, node.address, response)
}
func (dht *DHT) processPacket(p packetType, out chan string) {
	r, _ := decodeMsg(p.b)
	tid := r.T
	switch {
	case r.Y == "r":
		nodestring := r.R.Nodes
		nodes := parseNodesString(nodestring)
		for k, v := range nodes {
			node := new(Node)
			node.nid = Id(k)
			node.tid = tid
			node.address = v

			dht.Table <- node
		}
	case r.Y == "q":
		node := new(Node)
		node.tid = tid
		node.address = p.address
		node.nid = Id(r.A.Id)

		switch r.Q {
		case "ping":
			dht.replyPing(node)
		case "find_node":
			dht.replyError(node)
			// dht.replyFindNode(node)
		case "get_peers":
			infohash := r.A.InfoHash
			dht.replyGetPeers(node, infohash, out)
		case "announce_peer":
			infohash := r.A.InfoHash
			token := r.A.Token
			dht.replyAnnouncePeer(node, infohash, token, out)
		}
	}

}

func (dht *DHT) Run(out chan string) {
	packetChan := make(chan packetType)
	conn, err := listen(dht.Address)
	if err != nil {
		log.Fatal(err)
	}

	dht.Conn = conn
	dht.Port = conn.LocalAddr().(*net.UDPAddr).Port

	go reviceMsg(conn, packetChan)

	for _, host := range BOOTSTRAP {
		addr, _ := net.ResolveUDPAddr("udp4", host)
		node := new(Node)
		node.address = addr
		dht.Table <- node
	}

	log.V(1).Infof("开始DHT爬虫，Port:%d", dht.Port)

	for {
		select {
		case node := <-dht.Table:
			dht.sendFindNode(node.(*Node))
		case p := <-packetChan:
			dht.processPacket(p, out)
		}
	}

}
